package no.rmz.genetics;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class FieldValue {

    private String fieldname;
    private Class valueType;
    private Method setter;
    private Method getter;

    @Override
    public String toString() {
        return "[FieldValue   fieldName = " + fieldname
                + ", valueType = " + valueType
                + ", setter = " + setter
                + ", getter = " + getter + "]";
    }

    public void checkInvariants() {
        assert ( fieldname != null );
        assert ( valueType != null );
        assert ( setter != null );
        assert ( getter != null );
    }

    public boolean invariantsOk() {
        return ( ( fieldname != null )
                && ( valueType != null )
                && ( setter != null )
                && ( getter != null ) );
    }

    public FieldValue(String inFieldname) {
        fieldname = inFieldname;
    }

    public final Method getGetter() {
        return getter;
    }

    public final void setGetter(final Method getter) {
        this.getter = getter;
    }

    public final Method getSetter() {
        return setter;
    }

    public final void setSetter(final Method setter) {
        this.setter = setter;
    }

    public final String getFieldname() {
        return fieldname;
    }

    public final Class getValueType() {
        return valueType;
    }

    public final void setValueType(final Class c) {
        this.valueType = c;
    }

    /**
     * A database of genetically significant field values.
     *
     */
    final static class FieldValueDatabase {

        final static FieldValueDatabase instance = new FieldValueDatabase();
        private Map<Class, Map<String, FieldValue>> fieldValues;

        static {
            instance.reset();
        }

        public void reset() {
            fieldValues = new HashMap<>();
        }

        private boolean isGeneticSetOrGet(final Method m) {
            final String n = m.getName();
            return ( ( m.getAnnotation(Gene.class) != null )
                    && ( n.length() > 3 )
                    && ( n.startsWith("get") || n.startsWith("set") ) );
        }

        private void populateFieldMap(final Class c, final Map<String, FieldValue> values) {
            for (final Method m : c.getDeclaredMethods()) {
                if (isGeneticSetOrGet(m)) {
                    final String name = m.getName().substring(3);
                    final boolean isGet = m.getName().startsWith("get");
                    FieldValue fv = values.get(name);
                    if (fv == null) {
                        fv = new FieldValue(name);
                        values.put(name, fv);
                    }
                    if (isGet) {
                        // XXX Check parameters of m
                        // XXX assert not set previously
                        fv.setValueType(m.getReturnType());
                        fv.setGetter(m);
                    } else {
                        // XXX Check parameters of m
                        // XXX assert not set previously
                        fv.setSetter(m);
                    }
                }
            }
        }

        final synchronized public Collection<FieldValue> getFieldValues(final Class c) {
            if (!fieldValues.containsKey(c)) {
                final Map<String, FieldValue> values = new TreeMap<String, FieldValue>();
                fieldValues.put(c, values);
                populateFieldMap(c, values);
            }
            return fieldValues.get(c).values();
        }
    }

    //XXX This is a field value database, it is a singelton, but it should
    //    be in a separate class.
    final static public Collection<FieldValue> getFieldValues(final Class c) {
        return FieldValueDatabase.instance.getFieldValues(c);
    }

    public final static void reset() {
        FieldValueDatabase.instance.reset();
    }
}
